package com.zyl.file.platform;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.amazonaws.services.s3.model.PartSummary;
import com.zyl.file.constant.StateConstants;
import com.zyl.file.dto.FilePartInfoDTO;
import com.zyl.file.dto.UploadDTO;
import com.zyl.file.exception.MyException;
import com.zyl.file.po.StEquipment;
import com.zyl.file.po.StFile;
import com.zyl.file.util.AesUtils;
import com.zyl.file.util.DateUtils;
import com.zyl.file.util.FileEncryptUtils;
import com.zyl.file.util.Sm2Util;
import com.zyl.file.vo.UploadSplitVO;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.codec.Hex;
import org.springframework.beans.factory.annotation.Value;

import javax.annotation.Resource;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author zyl
 * @Description
 * @since 2024/3/18 15:28
 */
@Getter
@Setter
@NoArgsConstructor
@Slf4j
public class LocalFileStorage implements FileStorage {

    private String basePath;
    private String platform;
    private String platformType;
    private String domain;
    private String bucketName;

    public LocalFileStorage(StEquipment config) {
        platform = String.valueOf(config.getId());
        basePath = config.getBasePath();
        domain = config.getDomainName();
        platformType = String.valueOf(config.getStorageType());
        bucketName = config.getBasePath();
    }

    /**
     * 预签名url过期时间(ms)
     */
    public static final Long PRE_SIGN_URL_EXPIRE = 60 * 10 * 1000L;

    /**
     * 加密最大范围： 单位KB
     */
    @Value("${upload.encrypt.part-encrypt-size:800}")
    private static Integer PART_ENCRYPT_SIZE = 800;

    /**
     * aes加密标识符（用于区分加密、未加密数据）
     */
    @Value("${upload.encrypt.encrypt-index:<ENCRYPTED>}")
    private static String ENCRYPT_INDEX = "<ENCRYPTED>";

    /**
     * #国密（SM2）加密标识符（用于区分加密部分、未加密部分）
     */
    @Value("${upload.encrypt.sm2-encrypt-index:<SM2_ENCRYPTED>}")
    private static String SM2_ENCRYPT_INDEX = "<SM2_ENCRYPTED>";
//
//    @Resource
//    private ParamApi paramApi;

    private final Pattern pattern = Pattern.compile("^\\d+");

    @Override
    public String getPlatform() {
        return platform;
    }

    @Override
    public void setPlatform(String platform) {

    }

    @Override
    public StFile getByIdentifier(String identifier, Long id) {
        return null;
    }

    @Override
    public void initTask(StFile stFile) {
        String uploadId = IdUtil.randomUUID();
        stFile.setUploadId(uploadId);
        stFile.setBucketName(basePath);
    }

    @Override
    public String getPath(String key) {
        return StrUtil.format("{}/{}", basePath, key);
    }

    @Override
    public List<PartSummary> getTaskInfo(StFile stFile) {
        //此步骤只徐然返回的list的size对应已上传到数量就行，用于前端来判断续传前已经传了多少个子文件了
        List<PartSummary> exitPartList = new ArrayList<>();
        //1、根据index来合并指定temporaryFolder里面的分片文件
        File file = new File(stFile.getBasePath() + "/temporaryFolder" + File.separator
                + stFile.getBusiBatchNo() + File.separator
                + stFile.getFileMd5());
        File[] files = file.listFiles();
        if (null != files && files.length > 0) {
            for (File item : files) {
                PartSummary partSummary = new PartSummary();
                partSummary.setPartNumber(Integer.parseInt(item.getName().split("-")[0]));
                partSummary.setSize(item.length());
                exitPartList.add(partSummary);
            }
        }
        return exitPartList;
    }

    @Override
    public String genPreSignUploadUrl(StFile stFile) {
        StringBuffer nasUrl = new StringBuffer(stFile.getUrl());
        nasUrl.append("&identifier=" + stFile.getFileMd5());
        nasUrl.append("&fileName=" + stFile.getFilename() + "." + stFile.getExt());
        nasUrl.append("&equipmentId=" + stFile.getEquipmentId());
        nasUrl.append("&busiBatchNo=" + stFile.getBusiBatchNo());
        nasUrl.append("&id=" + stFile.getId());
        return nasUrl.toString();
    }

    @Override
    public FilePartInfoDTO uploadSplit(UploadSplitVO uploadSplitVO) {

        try {
            InputStream inputStream ;
            if (StateConstants.IS_ENCRYPT.equals(uploadSplitVO.getIsEncrypt()) && uploadSplitVO.getPartNumber()==1){
                inputStream = FileEncryptUtils.encryptStream(uploadSplitVO.getInputStream(), uploadSplitVO.getEncryptKey(), uploadSplitVO.getEncryptIndex());

            }else {
                inputStream = uploadSplitVO.getInputStream();
            }
            String url = basePath + "/temporaryFolder" + File.separator
                    + uploadSplitVO.getBusiBatchNo() + File.separator
                    + uploadSplitVO.getFileId() + File.separator;
            String partFileName = uploadSplitVO.getPartNumber() + "-" + uploadSplitVO.getFileName();
            Path path = Paths.get(url);
            if (Files.exists(path) && Files.isDirectory(path)) {
            } else {
                // 如果目录不存在，则创建新目录
                try {
                    Files.createDirectories(path);
                    log.info("目录已创建" + url);
                } catch (IOException e) {
                    log.info("无法创建目录",e);
                    throw new RuntimeException(e.getMessage(),e);
                }
            }
            File dir = FileUtil.file(url);
            File part = FileUtil.file(dir, partFileName);
            long partSize = uploadSplitVO.getInputStream().available();
            FileUtil.writeFromStream(inputStream, part);
            return new FilePartInfoDTO()
                    .setPartSize(partSize)
                    .setPartNumber(uploadSplitVO.getPartNumber())
                    .setCreateTime(new Date());
        } catch (Exception e) {
            log.error("上传分片失败",e);
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    @Override
    public void merge(StFile stFile) {
        //文件分片的目录
        String url = stFile.getBucketName() + File.separator +  "/temporaryFolder"  + File.separator
                + stFile.getBusiBatchNo() + File.separator
                + stFile.getId() + File.separator;
        //合并后文件目录
        String lastFile = stFile.getBucketName() + File.separator + DateUtils.getDate();
        //文件分片的目录
        //合并后的文件
        File newFile = FileUtil.file(lastFile,stFile.getFilename() + "." + stFile.getExt());
        //文件分片的目录
        File dir = FileUtil.file(url);
        File[] partFiles = dir.listFiles();
        // 对文件名列表进行排序，使用自定义比较器
        Arrays.sort(partFiles, new Comparator<File>() {
            @Override
            public int compare(File o1, File o2) {
                int number1 = extractNumber(o1.getName());
                int number2 = extractNumber(o2.getName());
                return Integer.compare(number1, number2);
            }

            // 提取文件名中的数字部分
            private int extractNumber(String fileName) {
                Matcher matcher = pattern.matcher(fileName);
                if (matcher.find()) {
                    return Integer.parseInt(matcher.group());
                } else {
                    throw new MyException("文件名不包含数字" + fileName);
                }
            }
        });
        log.info("排序分片{}",Arrays.stream(partFiles).map(File::getName).collect(Collectors.toList()));
        try (BufferedOutputStream out = FileUtil.getOutputStream(newFile)) {
            for (File file :partFiles) {
                FileUtil.writeToStream(file, out);
            }
        }catch (Exception e){
            log.info("文件合并出错",e);
            throw new RuntimeException(e.getMessage(),e);
        }
        log.info("合并完成,删除所有分片");
        if (log.isInfoEnabled()){
            log.info("删除所有分片");
        }
        FileUtil.del(url);
        //删除空文件夹
        deleteDirectory(new File(stFile.getBucketName() + File.separator +  "/temporaryFolder"  + File.separator
                + stFile.getBusiBatchNo()));
    }

    @Override
    public StFile upload(UploadDTO uploadDTO) {
        String url = basePath + File.separator + uploadDTO.getKey();
        FileUtil.writeFromStream(uploadDTO.getInputStream(), url);
        StFile stFile = new StFile().setBucketName(basePath)
                .setFilePath(uploadDTO.getFilePath())
                .setUrl(url);
        return stFile;
    }

    @Override
    public InputStream getFileStream(String key) {
        File file = new File(basePath + "/" +key);
        if (file.exists()){
            try {
                return new FileInputStream(file);
            } catch (FileNotFoundException e) {
                log.error("获取文件流失败",e);
                throw new RuntimeException(e.getMessage(), e);
            }
        }
        return null;
    }

    @Override
    public Long getFileSize(String key) {
        File file = new File(basePath + "/" +key);
        if (file.exists()){
            return file.length();
        }
        return null;
    }

    @Override
    public StFile replaceFile(UploadDTO uploadDTO) {
        try {
            // 删除原文件
            boolean deleteSuccess = FileUtil.del(basePath + File.separator + uploadDTO.getFilePath());
            if (!deleteSuccess) {
                // 如果删除失败，可以根据需要进行相应的处理
                log.error("删除原文件失败：" + uploadDTO.getFilePath());
            }
            // 重新写入新文件
            FileUtil.writeFromStream(uploadDTO.getInputStream(),basePath + File.separator + uploadDTO.getFilePath());
            log.info("文件替换成功");
        } catch (Exception e) {
            log.error("文件替换失败",e);
            throw new RuntimeException(e.getMessage(), e);
        }
        StFile stFile = new StFile();
        return stFile;
    }

    @Override
    public boolean delete(String url) {
        try {
            return FileUtil.del( basePath + "/" + url);
        } catch (Exception e) {
            log.error("删除文件失败",e);
            throw new RuntimeException(e.getMessage(),e);
        }
    }

    @Override
    public void download(StFile stFile, Consumer<InputStream> consumer) {

    }

    /**
     * 判断时候进行秒传
     * @return Result
     */
    private Boolean isSecondsUpload(){
//        Result<SysParamDTO> paramDtoResult = paramApi.searchValueByKey("FILE_UPLOAD_SECOND_PASS_SWITCH");
//        if (ObjectUtil.isNotEmpty(paramDtoResult.getData()) && "1".equals(paramDtoResult.getData().getValue())){
//            return true;
//        }
        return false;
    }

    /**
     * 加密文件
     * @param partFile 分片文件
     * @param encryptIndex 加密标识
     * @param encryptKey 加密key
     */
    private void encryptFile(File partFile, String encryptIndex, String encryptKey) {
        //临时路径
        String s = partFile.getParent() + "/" + UUID.fastUUID() + partFile.getName();
        // 清除文件内容
            try (InputStream fileInputStream = new FileInputStream(partFile);
                 FileOutputStream fileOutputStream = new FileOutputStream(s);
                 BufferedOutputStream bos = new BufferedOutputStream(fileOutputStream)) {
                //截取配置加密范围进行加密
                byte[] part = new byte[PART_ENCRYPT_SIZE * 1024];
                fileInputStream.read(part);
                byte[] encryptByte = new byte[0];
                //根据标识判断是否是SM2、还是AES
                if (encryptIndex.equals(ENCRYPT_INDEX)) {
                    String encrypt = AesUtils.encrypt(part, encryptKey);
                    encryptByte = encrypt.getBytes(StandardCharsets.UTF_8);
                } else if (encryptIndex.equals(SM2_ENCRYPT_INDEX)) {
                    String encrypt = Sm2Util.encrypt(part);
                    encryptByte = Hex.decode(encrypt);
                }
                //写入加密数据
                fileOutputStream.write(encryptByte);
                //写入标识符
                fileOutputStream.write(encryptIndex.getBytes(StandardCharsets.UTF_8));
                //写入未加密的部分
                byte[] buffer = new byte[1024 * 4];
                int bytesRead;
                while ((bytesRead = fileInputStream.read(buffer)) != -1) {
                    bos.write(buffer, 0, bytesRead);
                }
                //确保缓冲区数据被立即刷新到文件
                bos.flush();
            }catch (Exception e){
                if (log.isErrorEnabled()){
                    log.error("加密失败",e);
                    throw new RuntimeException(e.getMessage(),e);
                }
            }finally {
                // 文件1和文件2的路径
                Path source = Paths.get(s);
                Path destination = Paths.get(partFile.getPath());
                try {
                    //将加密后的文件复制到源文件中
                    Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING);
                } catch (IOException e) {
                    log.error("加密文件失败",e);
                    throw new RuntimeException(e.getMessage(),e);
                }
                //删除临时文件
                FileUtil.del(s);
            }
    }

    /**
     * 删除空文件夹
     * @param directoryToBeDeleted 文件对象
     * @return boolean
     */
    private static boolean deleteDirectory(File directoryToBeDeleted) {
        // 如果目录不存在，则直接返回 true
        if (!directoryToBeDeleted.exists()) {
            return true;
        }

        // 如果是文件而不是目录，则直接删除
        if (directoryToBeDeleted.isFile()) {
            return directoryToBeDeleted.delete();
        }

        // 递归删除目录中的内容
        File[] allContents = directoryToBeDeleted.listFiles();
        if (allContents != null) {
            for (File file : allContents) {
                deleteDirectory(file);
            }
        }

        // 删除空目录
        return directoryToBeDeleted.delete();
    }

}
